package com.example.demo.utils;

import com.example.demo.comm.RedisCache;
import com.example.demo.comm.SpringContextUtils;
import com.example.demo.comm.ThreadPoolComponent;
import com.example.demo.task.DownLoadThreadTask;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 多部件下负载
 *
 * @author dlp
 * @date 2024/09/06
 */
public class MultiPartDownLoadUtils {

    public static final int SEMIH_TIME = 30 * 60 * 60;
    private static Logger logger = LoggerFactory.getLogger(MultiPartDownLoadUtils.class);

    /**
     * 每段大小
     */
    public static final  int PART_COUNT = 10 * 1024 * 1024;

    private String downloadId;
    /**
     * 线程下载成功标志
     */
    public static int flag = 0;

    /**
     * 服务器请求路径
     */
    private String serverPath;
    /**
     * 本地路径
     */
    private String localPath;
    /**
     * 线程计数同步辅助
     */
    private CountDownLatch latch;

    // 程池
    public static ExecutorService executorService =  new ThreadPoolComponent().getFullIOExecutorService(5, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(80), new ThreadPoolExecutor.CallerRunsPolicy());

    public MultiPartDownLoadUtils(String downloadId, String serverPath, String localPath) {
        this.serverPath = serverPath;
        this.localPath = localPath;
        this.downloadId = downloadId;
    }

    public boolean executeDownLoad() {
        try {
            URL url = new URL(serverPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);//设置超时时间
            conn.setRequestMethod("GET");//设置请求方式
            conn.setRequestProperty("Connection", "Keep-Alive");
            int code = conn.getResponseCode();
            if (code != 200) {
                logger.error(String.format("无效网络地址：%s", serverPath));
                return false;
            }
            //服务器返回的数据的长度，实际上就是文件的长度,单位是字节
            int length = conn.getContentLength();  //文件超过2G会有问题
//            long length = getRemoteFileSize(serverPath);
            logger.info("文件总长度:" + length + "字节(B)");
            RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
            //指定创建的文件的长度
            raf.setLength(length);
            raf.close();
            //分割文件10MB
            int partSize = (int)(length / PART_COUNT);
            latch = new CountDownLatch(partSize);
            RedisCache bean = SpringContextUtils.getBean(RedisCache.class);
            bean.setCacheObject(downloadId+"_count",partSize,SEMIH_TIME,TimeUnit.SECONDS);
            bean.setCacheObject(downloadId,partSize,SEMIH_TIME,TimeUnit.SECONDS);
            for (int threadId = 1; threadId <= partSize; threadId++) {
                // 每一个线程下载的开始位置
                long startIndex = (threadId - 1) * PART_COUNT;
                // 每一个线程下载的开始位置
                long endIndex = startIndex + PART_COUNT - 1;
                if (threadId == partSize) {
                    //最后一个线程下载的长度稍微长一点
                    endIndex = length;
                }
                logger.info("线程" + threadId + "下载:" + startIndex + "字节~" + endIndex + "字节");
                executorService.execute(new DownLoadThreadTask(threadId,downloadId, startIndex, endIndex, latch,serverPath,localPath));
            }
            latch.await();
            if(flag == 0){
                return true;
            }
        } catch (Exception e) {
            logger.error(String.format("文件下载失败，文件地址：%s,失败原因：%s", serverPath, e.getMessage()), e);
        }
        return false;
    }




    /**
     * 内部方法，获取远程文件大小
     * @param remoteFileUrl
     * @return
     * @throws IOException
     */
    private long getRemoteFileSize(String remoteFileUrl) throws IOException {
        long fileSize = 0;
        HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();
        httpConnection.setRequestMethod("HEAD");
        int responseCode = 0;
        try {
            responseCode = httpConnection.getResponseCode();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (responseCode >= 400) {
            logger.debug("Web服务器响应错误!");
            return 0;
        }
        String sHeader;
        for (int i = 1;; i++) {
            sHeader = httpConnection.getHeaderFieldKey(i);
            if (sHeader != null && sHeader.equals("Content-Length")) {
                fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));
                break;
            }
        }
        return fileSize;
    }

    /**
     * 下载文件执行器 , 在需要的地方调用该方法即可
     * serverPath : 远程文件
     * localPath : 下载到本地指定文件夹
     * @param serverPathUrl
     * @return
     */
    public static String downLoad(String downloadId,String serverPathUrl , String localPath) {
        String[] names = serverPathUrl.split("\\.");
        if (names == null || names.length <= 0) {
            return null;
        }
        logger.info("文件下载url:{} 文件id:{},用户id:{}",serverPathUrl,downloadId,123);
        String extension = FilenameUtils.getExtension(serverPathUrl);
        String baseName = FilenameUtils.getBaseName(serverPathUrl);
        String loadPath = localPath+"/"+baseName+"_"+downloadId+"_rwd.download";
        String changeNamePath = localPath+"/"+baseName+"."+extension;
        MultiPartDownLoadUtils m = new MultiPartDownLoadUtils(downloadId,serverPathUrl, loadPath);
        long startTime = System.currentTimeMillis();
        boolean flag = false;
        try{
            flag = m.executeDownLoad();
            long endTime = System.currentTimeMillis();
            if(flag){
                logger.info("文件下载结束,共耗时" + (endTime - startTime)+ "ms");
                File file = new File(changeNamePath);
                boolean exists = file.exists();
                if (exists){
                    file.delete();
                }
                FileUtils.moveFile(new File(loadPath), file);
                return changeNamePath;
            }
            logger.warn("文件下载失败");
            return null;
        }catch (Exception ex){
            logger.error(ex.getMessage(),ex);
            return null;
        }finally {
            MultiPartDownLoadUtils.flag = 0; // 重置 下载状态
            if(!flag){
                File file = new File(loadPath);
                file.delete();
            }
        }
    }
}

